mod basic_ops;

use core::fmt::{Debug, Display};
use std::{cell::RefCell, ops::Range, rc::Rc, result};

use crate::{expr::Literal, BuiltinMacro, Expr};

use super::{env::EnvExt, Appliable, Env};

#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[error("Trying to convert {{{from}}} to {{{to}}}")]
    Convert {
        from: &'static str,
        to: &'static str,
    },
    #[error("Unable to convert {from} into {{{to}}}")]
    ConvertToHost { from: Value, to: &'static str },
    #[error("Unable to convert {{{from}}} into {to}")]
    ConvertFromHost { from: &'static str, to: Value },
    #[error("Type mismatch. expect {{{expect}}} got {{{get}}}")]
    TypeMismatch {
        expect: &'static str,
        get: &'static str,
    },
    #[error("Operation '{op}' is not supported for {which}")]
    OperationNotSupported {
        op: &'static str,
        which: &'static str,
    },
    #[error("Index({idx}) out of range. host is {} {}", target.start, target.end)]
    IndexOutOfRange { idx: i32, target: Range<usize> },
    #[error("Integer overflow {0}")]
    IntegerOverflow(i32),
}

pub type Result<T> = result::Result<T, Error>;

#[derive(Debug, Clone)]
pub enum Value {
    Nil,
    Boolean(bool),
    Integer(i32),
    Real(f32),
    String(String),
    Expr(Expr),
    BuiltinMacro(BuiltinMacro),
    Lambda(Box<Lambda>),
    BuiltinLambda(BuiltinLambda),
    List(Vec<Value>),
    Ref(*mut Value),
    Ptr(*mut u8),
}

macro_rules! impl_try_into {
    ($($tag: path) | + as $target: ty) => {
        impl TryInto<$target> for Value {
            type Error = super::Error;

            fn try_into(self) -> super::Result<$target> {
                match self {
                    $($tag(v) => Ok(v as $target),) +
                    v => Err(Error::ConvertToHost { from: v, to: std::any::type_name::<$target>() }.into()),
                }
            }
        }
    }
}

macro_rules! impl_from {
    ($src: ty as $tag: path) => {
        impl From<$src> for Value {
            fn from(value: $src) -> Self {
                $tag(value)
            }
        }
    };
}

macro_rules! impl_is {
    ($name: ident ($tag: path =>  $t: ty)) => {
        pub fn $name(&self, v: $t) -> bool {
            if let $tag(i) = self {
                *i == v
            } else {
                false
            }
        }
    };
}

impl_from!(bool as Value::Boolean);
impl_from!(i32 as Value::Integer);
impl_from!(f32 as Value::Real);
impl_from!(String as Value::String);

//impl_into!(Value::Boolean as bool);
impl_try_into!(Value::Integer as i32);
impl_try_into!(Value::Integer | Value::Real as f32);

impl_try_into!(Value::String as String);
impl_try_into!(Value::Ptr as *mut u8);

impl Value {
    pub const TYPE_NAME_NIL: &'static str = "nil";
    pub const TYPE_NAME_BOOLEAN: &'static str = "bool";
    pub const TYPE_NAME_INTEGER: &'static str = "int";
    pub const TYPE_NAME_REAL: &'static str = "real";
    pub const TYPE_NAME_STRING: &'static str = "string";
    pub const TYPE_NAME_EXPR: &'static str = "expr";
    pub const TYPE_NAME_BUILTIN: &'static str = "builtin";
    pub const TYPE_NAME_LAMBDA: &'static str = "lambda";
    pub const TYPE_NAME_LIST: &'static str = "list";
    pub const TYPE_NAME_PTR: &'static str = "ptr";
    pub const TYPE_NAME_REF: &'static str = "ref";
    pub const TYPE_NAME_OBJECT: &'static str = "object";

    pub fn type_name(&self) -> &'static str {
        match self {
            Value::Nil => Self::TYPE_NAME_NIL,
            Value::Boolean(_) => Self::TYPE_NAME_BOOLEAN,
            Value::Integer(_) => Self::TYPE_NAME_INTEGER,
            Value::Real(_) => Self::TYPE_NAME_REAL,
            Value::String(_) => Self::TYPE_NAME_STRING,
            Value::Expr(_) => Self::TYPE_NAME_EXPR,
            Value::BuiltinMacro(_) => Self::TYPE_NAME_BUILTIN,
            Value::List(_) => Self::TYPE_NAME_LIST,
            Value::Lambda(_) => Self::TYPE_NAME_LAMBDA,
            Value::BuiltinLambda(_) => Self::TYPE_NAME_LAMBDA,
            Value::Ptr(_) => Self::TYPE_NAME_PTR,
            Value::Ref(_) => Self::TYPE_NAME_REF,
        }
    }

    pub fn is_nil(&self) -> bool {
        matches!(self, Self::Nil)
    }

    pub fn is_true(&self) -> bool {
        matches!(self, Self::Boolean(true))
    }

    pub fn is_false(&self) -> bool {
        matches!(self, Self::Boolean(false))
    }

    impl_is!(is_integer(Self::Integer => i32));
    impl_is!(is_real(Self::Real => f32));

    pub fn apply(self, env: Rc<Env>, exprs: Vec<Expr>) -> super::Result<Self> {
        match self {
            Value::BuiltinMacro(m) => m(env, exprs),
            Value::Lambda(lambda) => lambda.apply(env, exprs),
            Value::BuiltinLambda(builtin_lambda) => builtin_lambda.0.borrow_mut().apply(env, exprs),
            _ => Ok(self),
        }
    }
}

impl Display for Value {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Boolean(v) => write!(f, "{v}"),
            Self::Integer(v) => write!(f, "{v}"),
            Self::Real(v) => write!(f, "{v}"),
            Self::String(s) => write!(f, "\"{s}\""),
            Self::Expr(expr) => write!(f, "'{}", expr),
            Self::List(l) => write!(
                f,
                "[{}]",
                l.iter()
                    .rev()
                    .map(|v| v.to_string())
                    .collect::<Vec<_>>()
                    .join(" ")
            ),
            _ => write!(f, "{}", self.type_name()),
        }
    }
}

impl From<Literal> for Value {
    fn from(value: Literal) -> Self {
        match value {
            Literal::Boolean(v) => Value::Boolean(v),
            Literal::Integer(v) => Value::Integer(v),
            Literal::Real(v) => Value::Real(v),
            Literal::String(v) => Value::String(v),
        }
    }
}

#[derive(Debug, Clone)]
pub struct Symbol(pub String);

pub struct Lambda {
    symbols: Vec<String>,
    local: Rc<Env>,
    body: Expr,
}

impl Lambda {
    pub fn new(env: Rc<Env>, symbols: Vec<String>, body: Expr) -> Self {
        Self {
            symbols,
            local: Env::new(env),
            body,
        }
    }

    fn apply(mut self: Box<Self>, env: Rc<Env>, mut exprs: Vec<Expr>) -> crate::EvalResult {
        let len = self.symbols.len().min(exprs.len());

        for _ in 0..len {
            self.local
                .set(self.symbols.pop().unwrap(), env.eval(exprs.pop().unwrap())?)
        }

        if self.symbols.is_empty() {
            self.local.eval(self.body)
        } else {
            Ok(Value::Lambda(self))
        }
    }
}

impl Clone for Lambda {
    fn clone(&self) -> Self {
        Self {
            symbols: self.symbols.clone(),
            local: self.local.deep_clone(),
            body: self.body.clone(),
        }
    }
}

impl Debug for Lambda {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "Lambda")
    }
}

#[derive(Clone)]
pub struct BuiltinLambda(pub Rc<RefCell<dyn Appliable>>);

impl Debug for BuiltinLambda {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "builtin lambda")
    }
}
